根據這份報告我們可以看到,事件驅動架構模型是一個熱門的主題。但是,要設計一個好的事件驅動架構很有挑戰性。
我們都很熟悉同步模型,一個請求直接對應一個回應,但在非同步的場景下,這套模型不完全適用。儘管如此,還是有一些設計模式可以作為參考。
這藍圖是從Mark Richards的投影片擷取出來的,用來描述事件驅動架構的各種設計模式。
在這篇文章中,我會介紹這些模式的內涵。
這個設計模式定義了一個頻道監控者,他會監視參與的訊息佇列上的用量和使用率。有些常用的遙測指標,例如:訊息佇列內待處理的訊息數量、訊息消費者的數量、和訊息處理速率等。
有了這些遙測指標就可以了解整個系統的工作負載和系統的健康程度。更重要的是,有了這些資訊就可以讓系統有能力自我治癒,例如:透過調節消費者的數量來提升處理速率或阻塞訊息生產者的訊息生產速度。
消費管理者模式定義了一個管理者角色,這個管理者會根據剛提到的遙測指標負責調節消費者的數量。
管理者訂閱監控者提供的指標並判斷當下的消費者有無能力處理事件。如果消費者速度不足,那管理者就會喚醒更多消費者來加速消化事件,反之亦然。
剛提到的管理者會啟用更多消費者來提升處理效能,但是,如果成本是個考量,那就無法單純使用更多消費者。畢竟,每個多產生的消費者都是一筆額外支出。
因此,可以採取另一種手段,控制生產者流速模式。
當消費速度跟不上訊息的產生速度,那麼就會有一個訊號送給所有生產者,讓生產者知道應該要減速。當所有待處理的訊息處理完畢,生產者可以再根據信號恢復原本的狀態。
這個設計模式不只在事件驅動架構下很有用,在其他系統設計的情境也依然實用。
這個設計模式有兩個目標:
我們先從第一個目標看起。
當很大量的訊息進入訊息佇列,我們通常會啟用更多消費者來加速消化,稱為水平擴展。這個方法在無狀態(stateless)情境下非常常見,但是許多事件都是有狀態的,而事件順序必須要被重視。若是訊息被分配到不同的處理者身上,就有可能因為處理速度不同而導致事件錯序。
舉例來說:
這些事件順序必須被保證,不然,就會買錯商品。那麼該如何保證在眾多消費者中的事件順序?
在這個設計模式下定義了一個新的角色,事件分派者(dispatcher)。分派者負責根據事件的種類將事件分配到對應的消費者上。也就是說,所有相同種類的事件會在同一個地方處理,以此來保證順序。
所有分派者會維護一個對應表,用來記錄每個事件種類對應的消費者。當消費者處理完事件,消費者可以回呼分派者,如此一來分派者就能知道每個消費者的處理速率,也就能夠根據消費者的處理速度作為事件種類分配的依據。
當事件消費者在處理過程中碰到錯誤,這錯誤可能無法立即修復,例如事件格式錯誤、或交易衝突等。因此,消費者會將事件發送至另外一個訊息佇列,通常稱為死信佇列(Dead letter queue),由工作流程處理者接手。
這個工作流程處理者要嘛試著修復錯誤、要嘛將事件儲存下來。此外,處理者會將這些狀態顯示在一個儀表板上並通知人類(對應窗口)接手。
錯誤處理窗口可以手動修復這些問題,並考慮將事件重新送入訊息佇列中。值得一提的是,如果是將事件重新送入佇列,那事件順序無法被保證。
這些是在設計一個事件驅動架構很常使用的設計模式。
我們可以透過這些模式設計出一個能夠自我監控及自我治癒的系統。這樣的系統會具備可擴展性、穩定、有效率且能夠容錯。
但在事件驅動架構下還有一些值得注意的設計模式沒有在這篇文章介紹到,因此我會留到明天。
順帶一提,這些設計模式雖然說起來有點生硬,但其實有些已經融合在常見的訊息佇列系統內了。例如:AWS的SQS就有提供死信佇列並可以透過Cloudwatch進行監控,而確保訊息順序的手段則是運用在SQS的FIFO模式。